#ifndef _CARGS_H
#define _CARGS_H
#include <stddef.h>

typedef int __cargs_int; 
typedef int __cargs_bool;
typedef double __cargs_decimal; 
extern const __cargs_bool __cargs_true; 
extern const __cargs_bool __cargs_false; 

struct _IO_FILE; 
typedef struct _IO_FILE FILE; 

typedef enum OptionType
{
    String = 1 << 0,
    Integer = 1 << 1,
    Decimal = 1 << 2,
    Boolean = 1 << 3, 
} 
OptionType; 

typedef struct OptionData
{
    OptionType type; 
    char shortOption; 
    char* longOption; 
    char* description; 
    void* sourcePtr; 
}
OptionData; 

typedef struct ParseContext
{
    OptionData* items; 
    size_t itemsCount; 
}
ParseContext; 

typedef enum ParseErrorCategory
{
    OptionDoesntExist,
    NonBooleanOptionDoesntHaveArgument,
    CombinedOptionsShouldBeBoolean,
    IntegerParsingError,
    DecimalParsingError,
}
ParseErrorCategory; 

typedef struct ParseError
{
    ParseErrorCategory category; 
    size_t argIndex;
    char optionShortName;  
} 
ParseError; 

typedef struct ParseResult
{
    char* programName; 

    size_t* stringOptions; 
    size_t stringOptionsCount; 
    char** stringValues; 

    size_t* integerOptions; 
    size_t integerOptionsCount; 
    __cargs_int* integerValues;

    size_t* decimalOptions;
    size_t decimalOptionsCount; 
    __cargs_decimal* decimalValues; 

    size_t* booleanOptions;
    size_t booleanOptionsCount;  
    
    char** freeValues; 
    size_t freeValuesCount; 

    ParseError* errors; 
    size_t errorsCount; 
}
ParseResult;  

///
/// Creates a boolean option. 
/// This option does not require a value in next arg. 
/// It is also possible to combine several boolean options in short form in one 
/// argument (-abc instead of -a -b -c).    
///
/// @param shortOption short name of the option (-o)
/// @param longOption long name of the option (--option)
/// @param source pointer to the boolean variable for initialization (NULL allowed)
/// @param description description of the option
///
const OptionData booleanArg(
    const char shortOption, 
    char* longOption, 
    __cargs_bool* source, 
    char* description); 

///
/// Creates an integer option.
/// Next argument will be validated: integer number is required.
/// 
/// @param shortOption short name of the option (-o)
/// @param longOption long name of the option (--option)
/// @param source pointer to the integer variable for initialization (NULL allowed)
/// @param description description of the option
///
const OptionData integerArg(
    const char shortOption, 
    char* longOption, 
    __cargs_int* source, 
    char* description); 

///
/// Creates a decimal option. 
/// Next argument will be validated: decimal number is required.
/// 
/// @param shortOption short name of the option (-o)
/// @param longOption long name of the option (--option)
/// @param source pointer to the decimal variable for initialization (NULL allowed)
/// @param description description of the option
///
const OptionData decimalArg(
    const char shortOption, 
    char* longOption, 
    __cargs_decimal* source, 
    char* description); 

///
/// Creates a string option. 
/// 
/// @param shortOption short name of the option (-o)
/// @param longOption long name of the option (--option)
/// @param source pointer to the char* variable for initialization (NULL allowed)
/// @param description description of the option
///
const OptionData stringArg(
    const char shortOption, 
    char* longOption, 
    char** source, 
    char* description); 

///
/// Parses the arguments.
///
/// @param context object with information about options
/// @param argc an amount of arguments
/// @param argv array of arguments
///
ParseResult* parseArgs(ParseContext* context, const int argc, char* argv[]); 

///
/// Frees the memory from ParseResult object. 
///
/// @param result ParseResult object to free
///
void freeResult(ParseResult* result); 

/// 
/// Function outputs an information about errors into a file
/// 
/// @param file file to output errors data
/// @param context object with information about options
/// @param result result of parsing
/// @param argc an amount of arguments
/// @param argv array of arguments
///
/// @return true - errors exist, false - no errors
///
const __cargs_bool fprintErrors(
    FILE* file, 
    ParseContext* context, 
    ParseResult* result, 
    const int argc, 
    char* argv[]);

/// 
/// Function outputs an information about errors into stderr
/// 
/// @param context object with information about options
/// @param result result of parsing
/// @param argc an amount of arguments
/// @param argv array of arguments
///
/// @return true - errors exist, false - no errors
///
const __cargs_bool printErrors(
    ParseContext* context, 
    ParseResult* result, 
    const int argc, 
    char* argv[]);

///
/// Function outputs information about arguments into a file
///
/// @param file file to output the arguments data
/// @param context object with information about options
///
void fprintHelp(FILE* file, ParseContext* context);

///
/// Function outputs information about arguments into stdout
///
/// @param context object with information about options
///
void printHelp(ParseContext* context);

#endif // _CARGS_H